Allow SV_PrimitiveID in ray tracing hit stages#11423
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds SV_PrimitiveID hit-stage support across semantics, parameter binding, IR legalization, backend emit paths (HLSL/GLSL/SPIR‑V/CUDA), and extensive regression/diagnostic tests. ChangesSV_PrimitiveID ray tracing hit-stage support
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Actionable comments posted: 4
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: ba7eab78-0f00-444e-ad98-ede136284665
📒 Files selected for processing (11)
source/slang/core.meta.slangsource/slang/slang-emit.cppsource/slang/slang-ir-glsl-legalize.cppsource/slang/slang-ir-hlsl-legalize.cppsource/slang/slang-ir-hlsl-legalize.hsource/slang/slang-ir-spirv-legalize.cppsource/slang/slang-parameter-binding.cpptests/hlsl-intrinsic/ray-tracing/rt-pipeline-intrinsics-ahit.slangtests/hlsl-intrinsic/ray-tracing/rt-pipeline-intrinsics-chit.slangtests/hlsl-intrinsic/ray-tracing/rt-pipeline-intrinsics-int.slangtests/hlsl-intrinsic/ray-tracing/sv-primitiveid-raytracing.slang
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: dc284f9c-3c3f-4e6c-9851-02e3cd7b8f34
📒 Files selected for processing (3)
tests/diagnostics/execution-model/sv-primitiveid-rt-invalid.slangtests/hlsl-intrinsic/ray-tracing/rt-pipeline-intrinsics-int.slangtests/hlsl-intrinsic/ray-tracing/sv-primitiveid-raytracing.slang
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
source/slang/slang-ir-legalize-varying-params.cpp (1)
821-841:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftNested mixed hit-input structs still bypass the PrimitiveID rewrite.
This classification only recurses through
primitiveIDOnlyStructFields. A nested field likeInner { uint pid : SV_PrimitiveID; float2 bary : SV_Barycentrics; }is treated asordinaryFields, so Line 840 returnsfalsebefore any use-walking happens andinner.pidnever reaches the new PrimitiveID substitution path.Example:
struct Outer { Inner inner; }; [shader("closesthit")] void main(Outer o) { use(o.inner.pid, o.inner.bary.x); }leaves the nestedSV_PrimitiveIDuntouched.Suggested fix: recurse into nested mixed structs as well and rebuild the surviving ordinary subtree at each level, or reject nested mixed carriers earlier with a diagnostic until that rewrite exists.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 57240942-3e40-42d9-be9c-bbc1950e55ea
📒 Files selected for processing (3)
source/slang/slang-ir-legalize-varying-params.cpptests/hlsl-intrinsic/ray-tracing/rt-pipeline-intrinsics-chit.slangtests/hlsl-intrinsic/ray-tracing/sv-primitiveid-raytracing.slang
|
[Agent] Addressed the CodeRabbit outside-diff nested mixed struct finding in f0e623f. The PrimitiveID struct legalizer now analyzes nested structs recursively, narrows the surviving ordinary subtree at each level, rewrites nested PrimitiveID field accesses to the primitive-index intrinsic, and reconstructs original structs for whole nested forwards. Added HLSL/DXIL/GLSL/CUDA coverage for both direct nested mixed field access and forwarding input.inner to a helper. Validation: cmake --build --preset debug --target slangc slang-test; build/Debug/bin/slang-test tests/hlsl-intrinsic/ray-tracing/sv-primitiveid-raytracing.slang; build/Debug/bin/slang-test tests/hlsl-intrinsic/ray-tracing/rt-pipeline-intrinsics-chit.slang; git diff --check. ./extras/formatting.sh --modified could not run because gersemi, clang-format, prettier, and shfmt are not installed in PATH. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
source/slang/slang-ir-legalize-varying-params.cpp (1)
1098-1128:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPreflight all PrimitiveID replacements before narrowing nested field-access types.
This helper mutates
fieldExtract/fieldAddresstypes at Line 1098 before it proves that every
SV_PrimitiveIDfield can be materialized. IfemitRayTracingPrimitiveIndexValue()later returns
nullptrfor one field, the function returnsfalseat Lines 1116-1117 or Lines 1127-1128 after
already changing IR types, so the caller observes “legalization failed” with partially rewritten IR
still in the entry point.Suggested fix
- for (auto update : fieldAccessTypeUpdates) - { - update.inst->setFullType( - replaceParamValueType(builder, update.inst->getFullType(), update.valueType)); - } - List<IRStructField*> replacementFields; List<IRInst*> replacementValues; auto getPrimitiveIDValueReplacement = [&](IRStructField* field) -> IRInst* { for (Index i = 0; i < replacementFields.getCount(); i++) @@ replacementFields.add(field); replacementValues.add(valueReplacement); return valueReplacement; }; + + for (auto access : primitiveIDFieldAccesses) + { + if (!getPrimitiveIDValueReplacement(access.field)) + return false; + } + + for (auto update : fieldAccessTypeUpdates) + { + update.inst->setFullType( + replaceParamValueType(builder, update.inst->getFullType(), update.valueType)); + } for (auto access : primitiveIDFieldAccesses) { auto valueReplacement = getPrimitiveIDValueReplacement(access.field); if (!valueReplacement)
♻️ Duplicate comments (1)
tests/hlsl-intrinsic/ray-tracing/sv-primitiveid-raytracing.slang (1)
35-42:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAdd direct SPIR-V coverage for the new nested-mixed entry points.
The new
main_chit_struct_nested_mixedandmain_chit_struct_nested_mixed_forwardmatrix omits
-target spirv-assembly, so the direct SPIR-V legalization path for these exact shapes is not
covered here.Suggested test additions
+//TEST:SIMPLE(filecheck=STRUCT_NESTED_MIXED_SPIRV):-target spirv-assembly -entry main_chit_struct_nested_mixed -stage closesthit +//TEST:SIMPLE(filecheck=STRUCT_NESTED_MIXED_FORWARD_SPIRV):-target spirv-assembly -entry main_chit_struct_nested_mixed_forward -stage closesthitAs per coding guidelines, tests should verify intended behavior and ensure new feature paths have corresponding coverage.
Source: Coding guidelines
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 00de2de8-0763-4cf7-bf46-25441a712a48
📒 Files selected for processing (2)
source/slang/slang-ir-legalize-varying-params.cpptests/hlsl-intrinsic/ray-tracing/sv-primitiveid-raytracing.slang
|
[Agent] Addressed CodeRabbit’s PrimitiveID legalization preflight finding in 16800db. Field-access type rewrites now happen only after all PrimitiveID field replacements have been preflighted. I also tightened the CUDA checks for the nested mixed direct and forwarded cases. I left the duplicate direct-SPIR-V coverage request unchanged per the recorded reviewer directive to keep the new SV_PrimitiveID SPIR-V directives unqualified. Validation: |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
[Agent] Addressed the latest automated review gap by extending the SV_PrimitiveID struct legalization to direct SPIR-V hit-stage entry points and adding unqualified SPIR-V test directives for the struct, nested struct, mixed struct, and forwarding cases. Validation: |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 0dbe4b06-5107-4f0c-a639-9afb13b4ae65
📒 Files selected for processing (4)
source/slang/slang-ir-glsl-legalize.cppsource/slang/slang-ir-legalize-varying-params.cppsource/slang/slang-ir-legalize-varying-params.htests/hlsl-intrinsic/ray-tracing/sv-primitiveid-raytracing.slang
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
source/slang/slang-ir-legalize-varying-params.h (1)
130-139:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win🟡 Gap:
valueEmitterparameter undocumented in function commentThe doc comment at lines 130-133 describes the struct-parameter rewriting behavior but does not explain the new optional
valueEmitterparameter added at line 139. A caller cannot determine from the header:
- What happens when
valueEmitterisnullptr(default backend-agnostic emission?)- When a caller should provide a custom emitter (backend-specific primitive-index intrinsic?)
- What the callback receives and must return
Suggestion: Extend the doc comment to document the
valueEmitterparameter, e.g., add before line 134:/// When `valueEmitter` is non-null, its callback is invoked for each primitive-ID /// field to emit the replacement value; when null, default emission is used.Source: Coding guidelines
source/slang/slang-ir-legalize-varying-params.cpp (1)
683-719:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftArray-carried
SV_PrimitiveIDstill bypasses this legalization.This recursion only descends into direct
IRStructTypefields. A hit-stage input likestruct Inner { uint pid : SV_PrimitiveID; }; struct Outer { Inner items[2]; };falls through the ordinary-field branch here, soinfo->containsPrimitiveIDstays false andtryLegalizeRayTracingPrimitiveIDStructParam()leaves the parameter untouched. That skips the PrimitiveID rewrite foritems[i].pidand can leak an unsupported signature to later backend passes.Suggested fix:
// Pseudocode if (auto nestedStructType = as<IRStructType>(field->getFieldType())) { ... } else if (auto arrayType = as<IRArrayTypeBase>(field->getFieldType())) { // Recurse into the element type/layout when the element is a struct, // or diagnose explicitly if aggregate carriers are intentionally unsupported. }Source: Coding guidelines
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 04110e8d-006a-46d2-b512-4fcc21e93462
📒 Files selected for processing (4)
source/slang/slang-ir-glsl-legalize.cppsource/slang/slang-ir-legalize-varying-params.cppsource/slang/slang-ir-legalize-varying-params.htests/hlsl-intrinsic/ray-tracing/sv-primitiveid-raytracing.slang
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: bc22e261-371f-4a53-9a65-52d9151adf2c
📒 Files selected for processing (2)
source/slang/slang-ir-glsl-legalize.cppsource/slang/slang-ir-legalize-varying-params.h
There was a problem hiding this comment.
Verdict: ✅ Clean — no significant issues found
The PR plumbs SV_PrimitiveID through ray-tracing intersection/anyhit/closesthit on SPIR-V (direct + via-GLSL), DXIL/HLSL, and CUDA/OptiX. Unsupported backends (Metal, WGSL) are diagnosed at parameter-binding time with Diagnostics::Unimplemented and have a negative regression test. The IR rewrite uses traverseUses (snapshot semantics), guards against malformed input by failing the rewrite rather than producing null IR, and routes through a synthesized canonical helper __slang_ray_tracing_primitive_index that carries per-target intrinsic decorations.
Changes Overview
Semantic allowlist (core.meta.slang, slang-parameter-binding.cpp)
- Adds
[require(intersection|anyhit|closesthit)]to thesv_primitiveidget accessor. - Carves
SV_PrimitiveIDout of the "noinparameters" rule for intersection/raygen/miss/callable and out of the "in is hit attributes" rule for anyhit/closesthit. For Metal/WGSL the carve-out emitsDiagnostics::Unimplementedinstead of accepting the input.
HLSL/DXIL legalization (slang-emit.cpp, slang-ir-hlsl-legalize.{cpp,h}, slang-ir-legalize-varying-params.{cpp,h})
- Renames
legalizeNonStructParameterToStructForHLSL→legalizeParametersForHLSL; the renamed pass now also callslegalizeRayTracingPrimitiveIDParamsForHLSL, which uses a newHLSLRayTracingPrimitiveIDParamLegalizeContext : EntryPointVaryingParamLegalizeContextto rewrite hit-stageSV_PrimitiveIDparameters and struct fields to calls of the synthesized__slang_ray_tracing_primitive_indexhelper. - Pass placement preserves the original "must run before existential layout" invariant.
Shared rewrite core (slang-ir-legalize-varying-params.cpp)
- New
tryLegalizeRayTracingPrimitiveIDParamandtryLegalizeRayTracingPrimitiveIDStructParamhandle both the flatuint pid : SV_PrimitiveIDand the nested-struct-field form, including mixed-content structs (where the struct is narrowed to its remaining ordinary fields). - New
RayTracingPrimitiveIDValueEmitterindirection lets a backend (currently SPIR-V direct) substitute its own emitter for the field-replacement value.
SPIR-V (slang-ir-glsl-legalize.cpp, slang-ir-spirv-legalize.cpp)
consolidateRayTracingParametersnow optionally pre-rewrites hit-stageSV_PrimitiveIDbefore consolidation. Direct SPIR-V usesDirectSPIRVPrimitiveIDEmitterContextto emit aBuiltinInputglobal param + load instead of routing throughgl_PrimitiveID.slang-ir-spirv-legalize.cppflips the address space of ansv_primitiveidsystem-value var toBuiltinInputfor hit stages.
CUDA/OptiX (slang-ir-legalize-varying-params.cpp)
CUDAEntryPointVaryingParamLegalizeContextcalls the shared rewriters inprocessParamand adds aSystemValueSemanticName::PrimitiveIDcase that emitsoptixGetPrimitiveIndex()via the synthesized helper.
Multi-entry-point support (slang-ir-legalize-varying-params.cpp)
processModulenow collects entry points before processing and adds ashouldProcessEntryPointvirtual hook so target-specific contexts can filter (the new HLSL context only processes hit stages).
Tests (6 files, ~817 added lines)
tests/hlsl-intrinsic/ray-tracing/sv-primitiveid-raytracing.slang(523 lines) covers all 5 backends across all 3 hit stages, plus int/uint, used/unused, name-collision with the synthesized helper, inout-style mutation, struct-wrapped, nested-struct, and mixed-content-struct forms.tests/diagnostics/execution-model/sv-primitiveid-rt-invalid.slangcovers raygen/miss/callable rejection, inout/out rejection on hit stages, the Metal/WGSL "Unimplemented" diagnostic, and bad-type rejection.tests/cuda/sv-primitiveid-raytracing.slangadds a focused CUDA closesthit case.rt-pipeline-intrinsics-{ahit,chit,int}.slangget new SVPID entry points checked against SPIR-VBuiltIn PrimitiveIdand DXILdx.op.primitiveIndex.i32.
Fixes #11197
Summary of the problem from the end user perspective
SV_PrimitiveIDcould not be used consistently as an input in ray tracing intersection, any-hit, and closest-hit shaders. Shaders that need the primitive index in those stages had to use backend-specific forms instead of the semantic spelling.Minimal repro shader; if applicable
Root cause
The semantic metadata and parameter binding rules did not allow
SV_PrimitiveIDin ray tracing hit stages. Backend legalization also needed target-specific handling so the value is not treated like payload or hit-attribute data.Solution in this PR
The PR allows
SV_PrimitiveIDin intersection, any-hit, and closest-hit stages. SPIR-V lowers it asBuiltIn PrimitiveIdinput, SPIR-V-via-GLSL routes throughgl_PrimitiveID, HLSL/DXIL emission replaces uses with the nativePrimitiveIndex()intrinsic, and CUDA/OptiX emission replaces uses withoptixGetPrimitiveIndex().Notes to the reviewers; where to focus on
The main review points are the semantic/stage allowlist changes, the parameter-binding behavior for ray tracing system-value inputs, and the SPIR-V, GLSL, HLSL, and CUDA legalization paths. Direct SPIR-V ray tracing entry points still reach the GLSL legalization pass, so the
shouldEmitSPIRVDirectly()guard in that pass is intentional.Reviewer Directives (maintained by agent)
SV_PrimitiveIDSPIR-V test directives unqualified by explicit-emit-spirv-directlyor-emit-spirv-via-glsl; the CI/test matrix covers both SPIR-V emission paths. (Allow SV_PrimitiveID in ray tracing hit stages jkwak-work/slang#218 (comment))Related PRs in the past